package registry

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"sync"
	"time"
)

const (
	ServerPort  = ":3000"
	ServicesURL = "http://localhost" + ServerPort + "/services"
)

type registry struct {
	registrations []Registration
	mutex         *sync.RWMutex
}

func (r *registry) add(reg Registration) error {
	r.mutex.Lock()
	r.registrations = append(r.registrations, reg)
	r.mutex.Unlock()
	err := r.sendRequriedService(reg)
	r.notify(patch{
		Added: []patchEntry{
			{
				reg.ServiceName,
				reg.ServiceURL,
			},
		},
	})
	return err
}

func (r *registry) notify(fullPatch patch) {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	for _, reg := range r.registrations {
		go func(reg Registration) {
			for _, reqService := range reg.RequiredServices {
				p := patch{Added: []patchEntry{}, Removed: []patchEntry{}}
				sendUpdate := false
				for _, added := range fullPatch.Added {
					if added.Name == reqService {
						p.Added = append(p.Added, added)
						sendUpdate = true
					}
				}
				for _, removed := range fullPatch.Removed {
					if removed.Name == reqService {
						p.Removed = append(p.Removed, removed)
						sendUpdate = true
					}
				}
				if !sendUpdate {
					continue
				}
				err := r.sendPatch(p, reg.ServiceUpdateURL)
				if err != nil {
					log.Println(err)
				}
			}
		}(reg)
	}
}

func (r registry) sendRequriedService(reg Registration) error {
	r.mutex.RLock()
	defer r.mutex.RUnlock()

	var p patch
	for _, serviceReg := range r.registrations {
		for _, reqService := range reg.RequiredServices {
			if serviceReg.ServiceName != reqService {
				continue
			}
			p.Added = append(
				p.Added,
				patchEntry{
					Name: serviceReg.ServiceName,
					URL:  serviceReg.ServiceURL,
				},
			)
		}
	}

	err := r.sendPatch(p, reg.ServiceUpdateURL)
	return err
}

func (r registry) sendPatch(p patch, url string) error {
	data, err := json.Marshal(p)
	if err != nil {
		return err
	}

	_, err = http.Post(url, "application/json", bytes.NewBuffer(data))
	return err
}

func (r *registry) remove(url string) error {
	for i, reg := range r.registrations {
		if reg.ServiceURL == url {
			r.notify(patch{
				Removed: []patchEntry{
					{
						r.registrations[i].ServiceName,
						r.registrations[i].ServiceURL,
					},
				},
			})
			r.mutex.Lock()
			defer r.mutex.Unlock()
			if i == len(r.registrations)-1 {
				r.registrations = r.registrations[:i]
				return nil
			}
			r.registrations = append(r.registrations[:i], r.registrations[i+1:]...)
			return nil
		}
	}
	return fmt.Errorf("没有找到服务, url: %s", url)
}

func (r *registry) heartbeat(freq time.Duration) {
	for {
		var wg sync.WaitGroup
		for _, reg := range r.registrations {
			wg.Add(1)
			go func(reg Registration) {
				defer wg.Done()
				success := true
				for attemps := 0; attemps < 3; attemps++ {
					resp, err := http.Get(reg.BeartbeatURL)
					if err != nil || resp.StatusCode != http.StatusOK {
						log.Printf(
							"Heartbeat check failed for %s, Status: %d, err: %v",
							reg.ServiceName, resp.StatusCode, err,
						)
						if success {
							success = false
							r.remove(reg.ServiceURL)
						}
						time.Sleep(time.Second)
						continue
					}
					log.Printf("Heartbeat check passed for %s", reg.ServiceName)
					if !success {
						r.add(reg)
					}
					break
				}
			}(reg)
			wg.Wait()
			time.Sleep(freq)
		}
	}
}

var once sync.Once

func SetupRegistryService() {
	once.Do(func() {
		go reg.heartbeat(3 * time.Second)
	})
}

var reg = registry{
	registrations: make([]Registration, 0),
	mutex:         new(sync.RWMutex),
}

type RegistryService struct{}

func (s RegistryService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println("Request received")
	switch r.Method {
	case http.MethodPost:
		dec := json.NewDecoder(r.Body)
		var r Registration

		if err := dec.Decode(&r); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		if err := reg.add(r); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	case http.MethodDelete:
		payload, err := io.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		url := string(payload)
		log.Printf("注销服务, url: %s\n", url)
		if err := reg.remove(url); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	default:
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
}
